-
Notifications
You must be signed in to change notification settings - Fork 2.6k
StringComparer Create(culture, CompareOptions) overload #16334
Conversation
| { | ||
| throw new ArgumentException(nameof(culture)); | ||
| } | ||
| return new CultureAwareComparer(culture, options); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit newline, per the style above
| internal CultureAwareComparer(CultureInfo culture, CompareOptions compareOptions) | ||
| { | ||
| _compareInfo = culture.CompareInfo; | ||
| _compareOptions = compareOptions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a shame you have to keep _ignoreCase around as it's now redundant, but I agree you can't remove it due to serialization.
|
This seems reasonable but @tarekgh can you review? There are a forest of various comparers, compareinfo's, cultureinfos and so forth - can you confirm this makes sense. note this APi was approved here |
| private readonly CompareInfo _compareInfo; // Do not rename (binary serialization) | ||
| private readonly bool _ignoreCase; // Do not rename (binary serialization) | ||
| private CompareOptions _compareOptions; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe adding this breaks serialization compat with desktop. It likely needs to be made Optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, yes it doesn't implement ISerializable. @ViktorHofer I guess this is the first point we have to consider cross version serialization from 2.0. Should we use [OptionalField] (with a Version?) and have the code check _ignoreCase first? Or switch this to ISerializable (which I assume could be done non breaking, but I don't know.) so we can set it to match _ignoreCase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also @ViktorHofer do we have a test strategy for 2.0<->2.1 serialization interop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is one of the [De]Serializ[ing|ed] attributes, but IIRC there was some issue with those that we fixed recently (?)
@Anipik if you're not famliar with binary serialization, read all these pages including this one that is relevant here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, the following would happen:
Core 2.1 --> Core2.0/NETFX: pass, additional field in the blob is ignored
NETFX/Core2.0 --> Core 2.1: throw, missing field in the blob
The easiest solution is as Stephen stated mark _compareOptions as optional. IMO the best solution would be to mark the type as ISerializable, get rid of the _ignoreCase field and during deserialization try to read _compareOptions from the serialization blob with GetValueNoThrow and if that fails read _ignoreCase from the blob. For serialization inside GetObjectData, both fields would needed to be written to the blob.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to clarify, ISerializable is usually the old way used in serialization and it is used more if you are changing the field names. anyway, we need to wait a little bit to tell what is the best way we should go with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one more thing, OptionalField is very important to have, otherwise will break deserialization on old desktop versions (4.7 for instance)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are using it on plenty of types in the BCL, e.g. on all Exception types when we need more control over the serialization process. In this case it makes sense as we can get rid of the extra bool fields which benefits all scenarios, even when you don't care about serialization at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can get rid of the extra bool fields which benefits all scenarios
This will break the deserialization on on the full framework. except if you are talking to manually store it when we are serializing it. either way, we have to handle it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I mean to manually store it, that's why I'm pushing for ISerializable.
| _compareOptions = compareOptions; | ||
| } | ||
|
|
||
| private CompareOptions Options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this property isn't needed. Just passt the _options field to the CompareInfo.Compare calls
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return new CultureAwareComparer(culture, ignoreCase); | ||
| } | ||
|
|
||
| public static StringComparer Create(CultureInfo culture, CompareOptions options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we introduce this new API I would expect the existing one static StringComparer StringComparer.Create(CultureInfo, bool) to be marked as obsolete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danmosemsft should I mark this as obsolete ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. it is still ok to use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not mark new API with the obsolete attribute, because historically it makes it hard for customers with large codebases and "warnings as errors" to upgrade their platforms. Instead you could open an issue in https://github.com/dotnet/platform-compat to flag it dynamically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I wasn't talking about ObsoleteAttribute but about the compat tooling.
|
please wait don't merge this one. |
|
This functionality is already supported in So you can do it through CompareInfo.GetStringComparer(options)We have 2 copies of CultureAwareComparer implementations now. would be better if we think to unify that and keep just one copy in coreclr and change corefx to call the coreclr one. For serialization, as @stephentoub pointed early this will break the desktop serialization. to fix that, will need to mark the newly introduced field as and then add the following method [OnDeserialized]
private void OnDeserializedMethod(StreamingContext context)
{
_options |= _ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
} |
| return new CultureAwareComparer(culture, ignoreCase); | ||
| } | ||
|
|
||
| public static StringComparer Create(CultureInfo culture, CompareOptions options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CompareOptions options [](start = 65, length = 22)
we should validate the options here
| } | ||
|
|
||
| if (!Enum.IsDefined(typeof(CompareOptions), options)) | ||
| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should check the options here as we are doing it in https://github.com/dotnet/corefx/blob/master/src/System.Globalization.Extensions/src/System/Globalization/Extensions.cs#L29
the reason is we shouldn't allow ordinal options here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why shouldn't we allow ordinal here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because if anyone want to use ordinal option should create OrdinalComparer and not culture aware comparer
private static readonly OrdinalCaseSensitiveComparer s_ordinal = new OrdinalCaseSensitiveComparer();
private static readonly OrdinalIgnoreCaseComparer s_ordinalIgnoreCase = new OrdinalIgnoreCaseComparer();
In reply to: 167785331 [](ancestors = 167785331)
| private void OnDeserializedMethod(StreamingContext context) | ||
| { | ||
| _options |= _ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know I have suggested this way but I was looking same time at issue on the full framework. after investigating it I found it is better to have CultureAwareComparer implement ISerializable something like
public CultureAwareComparer(SerializationInfo info, StreamingContext context)
{
_compareInfo = (CompareInfo) info.GetValue("_compareInfo", typeof(CompareInfo));
bool ignoreCase = info.GetBoolean("_ignoreCase");
var obj = info.GetValueNoThrow("_options", typeof(CompareOptions));
if (obj != null)
_options = (CompareOptions) obj;
// fix up the _options value in case we are getting old serialized object not having _options
_options |= ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("_compareInfo", _compareInfo);
info.AddValue("_options", _options);
info.AddValue("_ignoreCase", _(options & CompareOptions.IgnoreCase) != 0);
}note that you can fully remove _ignoreCase now too.
| private readonly CompareInfo _compareInfo; // Do not rename (binary serialization) | ||
| private readonly bool _ignoreCase; // Do not rename (binary serialization) | ||
|
|
||
| [OptionalField] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the type implements ISerializable and with that takes control of the serialization process, there is no need for an OptionalFieldAttribute here
| _options = compareOptions; | ||
| } | ||
|
|
||
| internal CultureAwareComparer(SerializationInfo info, StreamingContext context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be private as this constructor will only be invoked by the serialization engine via reflection and because the type is sealed
| { | ||
| int hashCode = _compareInfo.GetHashCode(); | ||
| return _ignoreCase ? ~hashCode : hashCode; | ||
| return (_options & CompareOptions.IgnoreCase) != 0 ? ~hashCode : hashCode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hashCode should include all _options, not just ignore case.
| [OptionalField] | ||
| private CompareOptions _options; | ||
|
|
||
| internal CultureAwareComparer(CultureInfo culture, bool ignoreCase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructor is not needed. The callers can use the other one and just pass in the right compare options.
| if ((options & CultureAwareComparer.ValidCompareMaskOffFlags) != 0) | ||
| { | ||
| throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onen last comment: you may move this check to the CultureAwareComparer constructor so you don't have to use ValidCompareMaskOffFlags here and everything will be well contained inside CultureAwareComparer
|
other than the mentioned comments, LGTM. thanks @Anipik. please don't forget when you expose the new API in the corefx, we'll need to get rid of the duplicate CultureAwareComparer implementation there. |
That will require moving |
No, we don't need to move anything to coreclr, we'll just need to change the implementation of public static StringComparer GetStringComparer(this CompareInfo compareInfo, CompareOptions options)to use the newly exposed API. |
| if (ignoreCase) | ||
| return new CultureAwareComparer(culture, CompareOptions.IgnoreCase); | ||
|
|
||
| return new CultureAwareComparer(culture, CompareOptions.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: maybe you can do it in one line using ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
| private CultureAwareComparer(SerializationInfo info, StreamingContext context) | ||
| { | ||
| _compareInfo = (CompareInfo)info.GetValue("_compareInfo", typeof(CompareInfo)); | ||
| bool ignoreCase = info.GetBoolean("ignoreCase"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignoreCase [](start = 47, length = 10)
you are missing the underscore prefix. should be _ignoreCae
|
just to tell we can fully get rid of the implementation in corefx as currently this implementation is used for netfx only and we are not building netfx packages for System.Globalization.Extensions anymore. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes look good to me but before you merge this I want you to verify that this doesn't break compatibility with netfx/netcoreapp2.0. Please add a test in System.Runtime.Seriazation.Formatters.Tests and run it with your local built coreclr package based on this commit.
|
@dotnet-bot test OSX10.12 x64 Checked Innerloop Build and Test |
| throw new ArgumentNullException(nameof(culture)); | ||
| } | ||
|
|
||
| return new CultureAwareComparer(culture, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Indentation is one space off.
Note that there is another duplicate implementation at |
@tarekgh The newly exposed API takes CultureInfo; but this API takes CompareInfo. I do not think there is a good way to map CompareInfo back to CultureInfo. |
we don't need the other implementation at all. so we should be fine here. Also we can get the culture object from the compareinfo object using |
If |
CultureInfo.GetCultureInfo is the cached version which should be good enough. the other idea is to expose override that work with the compareinfo from coreclr too. Also, this extension method is used only for who want to call CompareInfo.GetStringComparer which is not used in all our collection code nor is popular and people should use CultureAwareComparer and this extension. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anirudh verified locally that serialization is still working with netfx/netcoreapp2.0.
|
I am fine with the changes |
|
@jkotas if you are ok with changes, we can merge this one. |
|
|
||
| private readonly CompareInfo _compareInfo; // Do not rename (binary serialization) | ||
| private readonly bool _ignoreCase; // Do not rename (binary serialization) | ||
| private CompareOptions _options; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that one should be readonly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just checked with net471, they named the field the same. lucky us.
https://referencesource.microsoft.com/#mscorlib/system/stringcomparer.cs,140
It is not by luck. we have checked that. |
| { | ||
| if (culture == null) | ||
| { | ||
| throw new ArgumentException(nameof(culture)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this PR is extremely old but why doesn't this throw an ArgumentNullException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TylerBrinkley I think it is a mistake. Thanks for pointing it out. Generally it's best to open a new issue (or just PR) since posting on closed issues/PR's sometimes gets overlooked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did somebody follow-up on this? Was an issue created?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created #26570 to address this.
Related to https://github.com/dotnet/corefx/issues/395
Tests PR - dotnet/corefx#27051